package load_balance

import (
	"fmt"
	"net"
	"reflect"
	"sort"
	"time"
)

//default check setting
const (
	DefaultCheckMethod    = 0
	DefaultCheckTimeout   = 3
	DefaultCheckMaxErrNum = 2
	DefaultCheckInterval  = 5
)

type LoadBalanceCheckConf struct {
	observers         []Observer
	IpWeightMap       map[string]string // key=ip , v = weight
	loadBalanceIpList []string
	format            string
}

// 要同时检查 http、https、tcp、grpc 4 种类型的连接是否是通的， 统一用建立 tcp 连接的方式检查就行了，
// format 参数用于构造不同协议类型的 url，
func NewLoadBalanceCheckConf(format string, ipWeightMap map[string]string) (*LoadBalanceCheckConf, error) {
	var loadBalanceIpList []string
	//默认初始化
	for item := range ipWeightMap {
		loadBalanceIpList = append(loadBalanceIpList, item)
	}
	mConf := &LoadBalanceCheckConf{format: format, loadBalanceIpList: loadBalanceIpList, IpWeightMap: ipWeightMap}
	mConf.WatchConf()
	return mConf, nil
}

func (s *LoadBalanceCheckConf) Attach(o Observer) {
	s.observers = append(s.observers, o)
}

func (s *LoadBalanceCheckConf) GetLoadBalanceIpConf() []string {
	var confList []string
	for _, ip := range s.loadBalanceIpList {
		weight, ok := s.IpWeightMap[ip]
		if !ok {
			weight = "50" //默认weight
		}
		confList = append(confList, fmt.Sprintf(s.format, ip)+","+weight)
	}
	return confList
}

// 更新配置时，通知监听者也更新
func (s *LoadBalanceCheckConf) WatchConf() {
	//fmt.Println("watchConf")
	go func() {
		// 记录 ip 的错误数，只要有一次连接成功，错误数就归零，
		confIpErrNum := map[string]int{} // key=ip , v = errNum
		for {
			// 记录可以探活成功的 ip 列表
			var changedList []string
			for item := range s.IpWeightMap {
				conn, err := net.DialTimeout("tcp", item, time.Duration(DefaultCheckTimeout)*time.Second)
				//todo http statuscode
				if err == nil {
					conn.Close()
					confIpErrNum[item] = 0
				} else {
					confIpErrNum[item] += 1
				}
				if confIpErrNum[item] < DefaultCheckMaxErrNum {
					changedList = append(changedList, item)
				}
			}
			sort.Strings(changedList)
			sort.Strings(s.loadBalanceIpList)
			if !reflect.DeepEqual(changedList, s.loadBalanceIpList) {
				s.UpdateConf(changedList)
			}

			time.Sleep(time.Duration(DefaultCheckInterval) * time.Second)
		}
	}()
}

// 更新配置时，通知监听者也更新
func (s *LoadBalanceCheckConf) UpdateConf(conf []string) {
	//fmt.Println("UpdateConf", conf)
	s.loadBalanceIpList = conf
	s.NotifyAllObservers()
}

func (s *LoadBalanceCheckConf) NotifyAllObservers() {
	confList := s.GetLoadBalanceIpConf()
	for _, obs := range s.observers {
		obs.Update(confList)
	}
}
